package il.technion.cs236369.webserver;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.AccessDeniedException;

import org.apache.abdera.Abdera;
import org.apache.abdera.model.Feed;
import org.apache.abdera.model.Entry;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpDateGenerator;

import com.google.gson.JsonArray;
import com.google.gson.JsonPrimitive;

public abstract class DefaultHandler
{
	private static final String xhtmlHeader = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""+
			"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" +
			"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n";
	private static final String endl = System.getProperty("line.separator");
	private static String dirFormat = "";

	public DefaultHandler()
	{
	}

	/**
	 * Main request handling method
	 * @param request
	 * @return the response
	 */
	public static HttpResponse handle(ExtendedHttpRequest request)
	{
		//creating a response with the basic parameters
		HttpResponse response = CreateBaseForResponse();
		
		// if it has a convert header, the type is the type associated with json
		if (HasConvertHeader(request))
		{
			response.setHeader(HttpHeaders.CONTENT_TYPE,WebServer.map.getMimeType("json"));
		} // otherwise, check the content
		else
		{
			response.setHeader(HttpHeaders.CONTENT_TYPE,WebServer.map.getContentType(request));
		}
		
		// if the request is a post, reject
		if (request.GetType().equals("POST"))
		{
			ChangeResponseToBadRequest(response);
			return response;
		} 
		else
		{
			try
			{
				Header dirHeader = request.GetHttpRequest().getFirstHeader("DL-Format");
				if (dirHeader != null)
				{
					dirFormat = dirHeader.getValue().toLowerCase();
					switch (dirFormat)
					{
						case "atom":
							response.setHeader(HttpHeaders.CONTENT_TYPE, "application/atom+xml");
							break;
						case "json":
							response.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
							break;
						default:
					}
				}
				// handles the case of a GET request with no handler associated
				// catchers handle the different reasons for failing to fetch file 
				if (HasConvertHeader(request))
				{
					String fileContnetXML = GetFile(request.GetPath());
					if (fileContnetXML == null)
					{
						ChangeResponseToUnsupportedType(response);
						return response;
					}
					XML2JSON parser = new XML2JSON(fileContnetXML);

					StringEntity fileContnet = 
							new StringEntity(xhtmlHeader + parser.Convert(), "UTF-8");
					response.setHeader(HttpHeaders.CONTENT_LENGTH, 
										Long.toString(fileContnet.getContentLength()));
					response.setEntity(fileContnet);
				} 
				else
				{	
					StringEntity fileContnet = null;
					String fileContentString = GetFile(request.GetPath());
					if (fileContentString == null)
					{
						ChangeResponseToUnsupportedType(response);
						return response;
					}
					fileContnet = new StringEntity(fileContentString);
					response.setHeader(HttpHeaders.CONTENT_LENGTH, 
										Long.toString(fileContnet.getContentLength()));
					response.setEntity(fileContnet);
				}
				return response;
			}
			catch (AccessDeniedException e)
			{
				try 
				{
					ChangeResponseToNotFound(response, 
											"Access Denied to File " + request.GetPath());
					return response;
				}
				catch (UnsupportedEncodingException e1) 
				{
					System.err.println("DefaultHandler.handle: Unsupported Encoding Error in Access Denied.");
					e1.printStackTrace();
				}
			}
			catch (FileNotFoundException e)
			{
				try
				{
					ChangeResponseToNotFound(response, "File Not Found");
					return response;
				} catch (UnsupportedEncodingException e1)
				{
					System.err.println("DefaultHandler.handle: Unsupported Encoding Error in File Not Find.");
					e1.printStackTrace();
				}
			}  
			catch (IOException e)
			{
				try
				{
					ChangeResponseToNotFound(response, "Can't Read File");
					System.err.println("DefaultHandler.handle: IO Exception in file reading.");
					e.printStackTrace();
					return response;
				} 
				catch (UnsupportedEncodingException e1)
				{
					System.err.println("DefaultHandler.handle: Unsupported Encoding error in IO Exception.");
					e1.printStackTrace();
				}
			}
		}
		
		return response;
	}
	
	/**
	 * Checks if the request contains a convert header
	 * @param request
	 * @return true if found a convert header with the xml/json value,
	 * 		false otherwise
	 */
	private static boolean HasConvertHeader(ExtendedHttpRequest request)
	{
		for (Header header : request.GetHttpRequest().getAllHeaders())
		{
			if (header.getName().equalsIgnoreCase("convert") && 
				header.getValue().equals("xml/json"))
			{
				return true;
			}
		}
		
		return false;
	}

	/**
	 * This method changes the status of the response to be an error response
	 *  	corresponding to 400 error response and attaches a body which reports about the post error  
	 * @param response
	 */
	private static void ChangeResponseToBadRequest(HttpResponse response) 
	{
		response.setStatusCode(400);
		response.setReasonPhrase("Bad Request");
		response.setHeader(HttpHeaders.CONTENT_TYPE,"text/html");
		String errorPage = xhtmlHeader + BuildPageTitle("Error 400") + "<html>" + endl + "<body>" + endl +
				"\t<center><h1><font color=\"red\">Error 400:</h1></font>" + endl +
				"\t<p>Post handled by handlers only</p></center>" + endl +
				"</body>" + endl + "</html>";
		
		StringEntity error = null;
		
		try 
		{
			error = new StringEntity(errorPage);
			response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(error.getContentLength()));
		} 
		catch (UnsupportedEncodingException e) 
		{
			e.printStackTrace();
		}
		
		response.setEntity(error);
	}

	/**
	 * This method Changes the status of the response to be an error response
	 *  corresponding to 404 error response and attaches a body which reports about the error  
	 * @param response the response to change
	 * @throws UnsupportedEncodingException if the message is encoded in a strange fashion
	 */
	private static void ChangeResponseToNotFound(HttpResponse response,
											String message) 
											throws UnsupportedEncodingException 
	{
		response.setStatusCode(404);
		response.setReasonPhrase("Not Found");
		response.setHeader(HttpHeaders.CONTENT_TYPE,"text/html");
		String errorPage = xhtmlHeader + BuildPageTitle("Error 404") + "<body>" + endl + 
				"\t<center><h1><font color=\"red\">Error  404: Not Found</font></h1>" + endl +
				"\t<p>" + message + " </p></center>" + endl +
				"</body>" + endl + "</html>";
		StringEntity error = new StringEntity(errorPage);
		response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(error.getContentLength()));
		response.setEntity(error);
	}

	/**
	 * This method changes the status of the response to be an error response
	 *  	corresponding to 415 error response and attaches a body which reports the error  
	 * @param response
	 */
	private static void ChangeResponseToUnsupportedType(HttpResponse response) 
	{
		response.setStatusCode(415);
		response.setReasonPhrase("Unsupported Media Type");
		response.setHeader(HttpHeaders.CONTENT_TYPE,"text/html");
		String errorPage = xhtmlHeader + BuildPageTitle("Error 415") + "<html>" + endl + "<body>" + endl +
				"\t<center><h1><font color=\"red\">Error 415:</h1></font>" + endl +
				"\t<p>You requested a format this website does not support.</p></center>" + endl +
				"</body>" + endl + "</html>";
		
		StringEntity error = null;
		
		try 
		{
			error = new StringEntity(errorPage);
			response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(error.getContentLength()));
		} 
		catch (UnsupportedEncodingException e) 
		{
			e.printStackTrace();
		}
		
		response.setEntity(error);
	}

	private static String GetNativeDirectoryStructure(File file)
	{
		StringBuilder dirStructure = new StringBuilder();
		
		dirStructure.append(xhtmlHeader).append(BuildPageTitle("Folder Content"));
		dirStructure.append("<body>" + endl +
							"\t<p>The direcotry contains the following files and directories.</p>");
		dirStructure.append(endl);
		for (String fileName : file.list())
		{
			dirStructure.append("\t<p>").append(fileName).append("</p>").append(endl);
		}
		dirStructure.append("</body>" + endl + "</html>");
		return dirStructure.toString();
	}

	private static String GetAtomDirectoryStructure(File file)
	{
		String[] filesInDir = file.list();
		Abdera base = new Abdera();
		Feed feed = base.newFeed();
		
		feed.setTitle("Directory List");
		feed.setUpdated(new HttpDateGenerator().getCurrentDate());
		
		for (String fileInDir : filesInDir)
		{
			Entry entry = feed.addEntry();
			entry.setTitle(fileInDir);
		}
		
		return feed.toString();
	}
	
	private static String GetJsonDirectoryStructure(File file)
	{
		JsonArray jsonArr = new JsonArray();
		String[] filesInDir = file.list();
		
		jsonArr.add(new JsonPrimitive("Directory List"));
		jsonArr.add(new JsonPrimitive("Updated on:" + new HttpDateGenerator().getCurrentDate()));
		
		for (String fileInDir : filesInDir)
		{
			jsonArr.add(new JsonPrimitive(fileInDir));
		}
		
		return jsonArr.getAsString();
	}
	
	private static String GetDirectoryStructure(File file)
	{
		switch (dirFormat)
		{
			case "atom":
				return GetAtomDirectoryStructure(file);
			case "json":
				return GetJsonDirectoryStructure(file);
			case "":
				return GetNativeDirectoryStructure(file);
			default:
				return null;
		}
	}

	/**
	 * Returns the content of the given file
	 * 	Assumes file exists
	 * @param file the file to fetch
	 * @return
	 * @throws FileNotFoundException
	 * @throws AccessDeniedException
	 * @throws IOException
	 */
	private static String GetFileContent(File file) throws FileNotFoundException,
													AccessDeniedException,
													IOException
	{
		StringBuilder fileContent = new StringBuilder();

		// else process as a file to read
		BufferedReader reader = new BufferedReader(new FileReader(file));
		String line  = null;

		while((line = reader.readLine()) != null)
		{
		   fileContent.append(line).append(endl);
		}
		
		reader.close();
		
		return fileContent.toString();
	}
	
	/**
	 * Tries to fetch a file and simply write it into a string
	 * If path points to a directory returns a html page which reports the content of the directory
	 * If fails throws an exception
	 * @param path the path to return from
	 * @return the file content or directory structure, null if the dir format is unsupported
	 * @throws FileNotFoundException couldn't find the file in the path
	 * @throws AccessDeniedException not authorised to view file or directory
	 * @throws IOException error in reading or closing file
	 */
	private static String GetFile(String path) throws FileNotFoundException, 
												AccessDeniedException, 
												IOException
	{
		File file = new File(path);

		// if no file or directory, eject mailman...
		if (!file.exists() && !file.isDirectory())
		{
			throw new FileNotFoundException();
		}

		// if directory, list files
		if (file.isDirectory())
		{
			return GetDirectoryStructure(file);
		} // otherwise, process file content
		else
		{
			return GetFileContent(file);
		}
		
	}
	
	/**
	 * creates a response with the basic fields (date, status line, connection header)
	 * @return
	 */
	private static HttpResponse CreateBaseForResponse() 
	{
//		Creating the request according to the requirement, for example:
//		HTTP/1.1 200 OK
//		Date: Sun, 02 Mar 2008 04:37:22 GMT
//		Content-Length: 3545
//		Connection: close
//		Content-Type: image/gif
		HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK");
		response.setHeader(HttpHeaders.DATE, new HttpDateGenerator().getCurrentDate());
		response.setHeader(HttpHeaders.CONNECTION, HTTP.CONN_CLOSE);
		//response.setHeader(HttpHeaders.CONTENT_TYPE, "text/html; charset=utf-16");
		return response;
	}

	/**
	 * Returns an html head with included title
	 * @param title the title of the page
	 * @return an html head including opening html tag,
	 * 		opening and closing head and title tags
	 */
	private static String BuildPageTitle(String title)
	{
		return "<html>" + endl + "\t<head>" + endl + "\t\t<title>" + title +
				"</title>" + endl + "\t</head>" + endl + endl;
	}
	
}
